/*-------------------------------------------------------------------------
 *
 * gistget.c
 *	  从GiST扫描中提取元组。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/access/gist/gistget.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/gist_private.h"
#include "access/relscan.h"
#include "lib/pairingheap.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "utils/float.h"
#include "utils/memutils.h"
#include "utils/rel.h"

/*
 * gistkillitems() -- 为索引扫描调用者告知我们已被杀死的项目设置 LP_DEAD 状态。
 *
 * 我们在此重新读取页面，因此检查页面 LSN 非常重要。如果自上次读取以来页面已被修改（由 LSN 确定），我们无法标记任何条目，因为旧条目可能已被清除，而 TID 可能已被完全不同的堆元组重新使用。
 */
static void fc_gistkillitems(IndexScanDesc fc_scan)
{
	GISTScanOpaque fc_so = (GISTScanOpaque) fc_scan->opaque;
	Buffer		fc_buffer;
	Page		fc_page;
	OffsetNumber fc_offnum;
	ItemId		fc_iid;
	int			fc_i;
	bool		fc_killedsomething = false;

	Assert(fc_so->curBlkno != InvalidBlockNumber);
	Assert(!XLogRecPtrIsInvalid(fc_so->curPageLSN));
	Assert(fc_so->killedItems != NULL);

	fc_buffer = ReadBuffer(fc_scan->indexRelation, fc_so->curBlkno);
	if (!BufferIsValid(fc_buffer))
		return;

	LockBuffer(fc_buffer, GIST_SHARE);
	gistcheckpage(fc_scan->indexRelation, fc_buffer);
	fc_page = BufferGetPage(fc_buffer);

	/*
	 * 如果页面 LSN 不同，则意味着自上次读取以来页面已被修改。killedItems 可能无效，因此 LP_DEAD 提示应用是不安全的。
	 */
	if (BufferGetLSNAtomic(fc_buffer) != fc_so->curPageLSN)
	{
		UnlockReleaseBuffer(fc_buffer);
		fc_so->numKilled = 0;		/* 重置计数器 */
		return;
	}

	Assert(GistPageIsLeaf(fc_page));

	/*
	 * 将所有 killedItems 标记为已死亡。我们不需要额外的重新检查，因为如果页面已被修改，curPageLSN 一定已更改。
	 */
	for (fc_i = 0; fc_i < fc_so->numKilled; fc_i++)
	{
		fc_offnum = fc_so->killedItems[fc_i];
		fc_iid = PageGetItemId(fc_page, fc_offnum);
		ItemIdMarkDead(fc_iid);
		fc_killedsomething = true;
	}

	if (fc_killedsomething)
	{
		GistMarkPageHasGarbage(fc_page);
		MarkBufferDirtyHint(fc_buffer, true);
	}

	UnlockReleaseBuffer(fc_buffer);

	/*
	 * 始终重置扫描状态，以便我们不在其他页面上查找相同的项目。
	 */
	fc_so->numKilled = 0;
}

/*
 * gistindex_keytest() -- 这个索引元组是否满足扫描键？
 *
 * 索引元组可能代表一个堆元组或一个较低的索引页面，具体取决于包含页面是否为叶页面。
 *
 * 成功返回堆元组时，*recheck_p 被设置以指示是否需要重新检查 quals。当任何 consistent() 函数请求时，我们会进行重新检查。检查非叶条目时重新检查没有意义，因为如果有任何疑问，我们必须访问较低的索引页面。同样，*recheck_distances_p 被设置以指示距离是否需要重新检查，并且对于非叶条目也会被忽略。
 *
 * 如果我们正在进行有序扫描，则在返回成功之前，so->distances[] 被填充了来自 distance() 函数的距离数据。
 *
 * 在将键传递给 sk_funcs（实际上是 opclass Consistent 或 Distance 方法）之前，我们必须在 IndexTuple 中解压缩该键。
 *
 * 请注意，此函数始终在短暂的内存上下文中调用，因此我们无需担心清理已分配的内存，无论是在这里还是在任何 Consistent 或 Distance 方法的实现中。
 */
static bool fc_gistindex_keytest(IndexScanDesc fc_scan,
				  IndexTuple fc_tuple,
				  Page fc_page,
				  OffsetNumber fc_offset,
				  bool *fc_recheck_p,
				  bool *fc_recheck_distances_p)
{
	GISTScanOpaque fc_so = (GISTScanOpaque) fc_scan->opaque;
	GISTSTATE  *fc_giststate = fc_so->giststate;
	ScanKey		fc_key = fc_scan->keyData;
	int			fc_keySize = fc_scan->numberOfKeys;
	IndexOrderByDistance *fc_distance_p;
	Relation	fc_r = fc_scan->indexRelation;

	*fc_recheck_p = false;
	*fc_recheck_distances_p = false;

	/*
	 * 如果这是来自 9.1 之前的剩余无效元组，则将其视为与最小可能距离匹配。这意味着我们将始终跟随它到引用的页面。
	 */
	if (GistTupleIsInvalid(fc_tuple))
	{
		int			fc_i;

		if (GistPageIsLeaf(fc_page))	/* 不应该发生 */
			elog(ERROR, "invalid GiST tuple found on leaf page");
		for (fc_i = 0; fc_i < fc_scan->numberOfOrderBys; fc_i++)
		{
			fc_so->distances[fc_i].value = -get_float8_infinity();
			fc_so->distances[fc_i].isnull = false;
		}
		return true;
	}

	/* 检查它是否根据 Consistent 函数匹配 */
	while (fc_keySize > 0)
	{
		Datum		fc_datum;
		bool		fc_isNull;

		fc_datum = index_getattr(fc_tuple,
							  fc_key->sk_attno,
							  fc_giststate->leafTupdesc,
							  &fc_isNull);

		if (fc_key->sk_flags & SK_ISNULL)
		{
			/*
			 * 在非叶页面上，我们无法得出子项没有 NULL 值的结论，因为在 GiST 中的假设：union (VAL, NULL) 是 VAL。
			 * 但是，如果在非叶页面上键为 NULL，则所有子项都是 NULL。
			 */
			if (fc_key->sk_flags & SK_SEARCHNULL)
			{
				if (GistPageIsLeaf(fc_page) && !fc_isNull)
					return false;
			}
			else
			{
				Assert(fc_key->sk_flags & SK_SEARCHNOTNULL);
				if (fc_isNull)
					return false;
			}
		}
		else if (fc_isNull)
		{
			return false;
		}
		else
		{
			Datum		fc_test;
			bool		fc_recheck;
			GISTENTRY	fc_de;

			gistdentryinit(fc_giststate, fc_key->sk_attno - 1, &fc_de,
						   fc_datum, fc_r, fc_page, fc_offset,
						   false, fc_isNull);

			/*
			 * 调用 Consistent 函数来评估测试。参数是索引数据（作为 GISTENTRY*）、比较数据、比较操作符的策略编号和来自 pg_amop 的子类型，以及重检查标志。
			 *
			 * （目前没有必要传递子类型，因为它始终为零，但为了未来可能的使用，还是传递它吧。）
			 *
			 * 我们将重检查标志初始化为 true（安全的假设），以防 Consistent 函数忘记设置它。
			 */
			fc_recheck = true;

			fc_test = FunctionCall5Coll(&fc_key->sk_func,
									 fc_key->sk_collation,
									 PointerGetDatum(&fc_de),
									 fc_key->sk_argument,
									 Int16GetDatum(fc_key->sk_strategy),
									 ObjectIdGetDatum(fc_key->sk_subtype),
									 PointerGetDatum(&fc_recheck));

			if (!DatumGetBool(fc_test))
				return false;
			*fc_recheck_p |= fc_recheck;
		}

		fc_key++;
		fc_keySize--;
	}

	/* 好的，它通过了 --- 现在让我们计算距离 */
	fc_key = fc_scan->orderByData;
	fc_distance_p = fc_so->distances;
	fc_keySize = fc_scan->numberOfOrderBys;
	while (fc_keySize > 0)
	{
		Datum		fc_datum;
		bool		fc_isNull;

		fc_datum = index_getattr(fc_tuple,
							  fc_key->sk_attno,
							  fc_giststate->leafTupdesc,
							  &fc_isNull);

		if ((fc_key->sk_flags & SK_ISNULL) || fc_isNull)
		{
			/* 假设距离计算为 null */
			fc_distance_p->value = 0.0;
			fc_distance_p->isnull = true;
		}
		else
		{
			Datum		fc_dist;
			bool		fc_recheck;
			GISTENTRY	fc_de;

			gistdentryinit(fc_giststate, fc_key->sk_attno - 1, &fc_de,
						   fc_datum, fc_r, fc_page, fc_offset,
						   false, fc_isNull);

			/*
			 * 调用 Distance 函数来评估距离。参数是索引数据（作为 GISTENTRY*）、比较数据、排序操作符的策略编号和来自 pg_amop 的子类型，以及重检查标志。
			 *
			 * （目前没有必要传递子类型，因为它始终为零，但为了未来可能的使用，还是传递它吧。）
			 *
			 * 如果函数设置了重检查标志，返回的距离是实际距离的下界，需要重新检查。我们将标志初始化为 'false'。这个标志是在 9.5 版本中添加的；在此之前编写的距离函数不会知道这个标志，但预计不会有损失。
			 */
			fc_recheck = false;
			fc_dist = FunctionCall5Coll(&fc_key->sk_func,
									 fc_key->sk_collation,
									 PointerGetDatum(&fc_de),
									 fc_key->sk_argument,
									 Int16GetDatum(fc_key->sk_strategy),
									 ObjectIdGetDatum(fc_key->sk_subtype),
									 PointerGetDatum(&fc_recheck));
			*fc_recheck_distances_p |= fc_recheck;
			fc_distance_p->value = DatumGetFloat8(fc_dist);
			fc_distance_p->isnull = false;
		}

		fc_key++;
		fc_distance_p++;
		fc_keySize--;
	}

	return true;
}

/*
 * 扫描所有在 *pageItem 所标识的 GiST 索引页上的项，并将它们插入到队列中（或者直接输出到输出区域）
 *
 * scan: 我们正在执行的索引扫描
 * pageItem: 标识要扫描的索引页的搜索队列项
 * myDistances: 与 pageItem 相关的距离数组，或 NULL 在根部
 * tbm: 如果不为 NULL，gistgetbitmap 的输出位图
 * ntids: 如果不为 NULL，gistgetbitmap 的输出元组计数器
 *
 * 如果 tbm/ntids 不为 NULL，我们正在执行 amgetbitmap 扫描，堆元组应直接报告到位图中。如果它们为 NULL，我们正在进行普通或有序的索引扫描。对于普通的索引扫描，堆元组 TIDs 返回到 so->pageData[] 中。对于有序索引扫描，堆元组 TIDs 被推入单独的搜索队列项中。在索引仅扫描中，重建的索引元组与 TIDs 一起返回。
 *
 * 如果我们检测到索引页自从我们在父节点中看到其下链接后已经分裂，我们会将其新的右兄弟推入队列，以便下一个处理兄弟节点。
 */
static void fc_gistScanPage(IndexScanDesc fc_scan, GISTSearchItem *fc_pageItem,
			 IndexOrderByDistance *fc_myDistances, TIDBitmap *fc_tbm, int64 *fc_ntids)
{
	GISTScanOpaque fc_so = (GISTScanOpaque) fc_scan->opaque;
	GISTSTATE  *fc_giststate = fc_so->giststate;
	Relation	fc_r = fc_scan->indexRelation;
	Buffer		fc_buffer;
	Page		fc_page;
	GISTPageOpaque fc_opaque;
	OffsetNumber fc_maxoff;
	OffsetNumber fc_i;
	MemoryContext fc_oldcxt;

	Assert(!GISTSearchItemIsHeap(*fc_pageItem));

	fc_buffer = ReadBuffer(fc_scan->indexRelation, fc_pageItem->blkno);
	LockBuffer(fc_buffer, GIST_SHARE);
	PredicateLockPage(fc_r, BufferGetBlockNumber(fc_buffer), fc_scan->xs_snapshot);
	gistcheckpage(fc_scan->indexRelation, fc_buffer);
	fc_page = BufferGetPage(fc_buffer);
	TestForOldSnapshot(fc_scan->xs_snapshot, fc_r, fc_page);
	fc_opaque = GistPageGetOpaque(fc_page);

	/*
	 * 检查我们是否需要跟随右链接。如果页面自从我们访问父节点后被并发分裂，我们需要跟随它（在这种情况下 parentlsn < nsn），或者如果系统在页面分裂后但在下链接插入到父节点之前崩溃。
	 */
	if (!XLogRecPtrIsInvalid(fc_pageItem->data.parentlsn) &&
		(GistFollowRight(fc_page) ||
		 fc_pageItem->data.parentlsn < GistPageGetNSN(fc_page)) &&
		fc_opaque->rightlink != InvalidBlockNumber /* 计算前一个数据项的长度 */ )
	{
		/* 页面已分裂，跟随右链接以添加页面 */
		GISTSearchItem *fc_item;

		/* 在根部时不能发生这种情况 */
		Assert(fc_myDistances != NULL);

		fc_oldcxt = MemoryContextSwitchTo(fc_so->queueCxt);

		/* 为右兄弟索引页创建新的 GISTSearchItem */
		fc_item = palloc(SizeOfGISTSearchItem(fc_scan->numberOfOrderBys));
		fc_item->blkno = fc_opaque->rightlink;
		fc_item->data.parentlsn = fc_pageItem->data.parentlsn;

		/* 将其插入队列，使用与此页面相同的距离 */
		memcpy(fc_item->distances, fc_myDistances,
			   sizeof(fc_item->distances[0]) * fc_scan->numberOfOrderBys);

		pairingheap_add(fc_so->queue, &fc_item->phNode);

		MemoryContextSwitchTo(fc_oldcxt);
	}

	/*
	 * 检查在我们看到下行链接后页面是否被删除。已删除页面上没有有趣的内容。请注意，我们必须在检查并发拆分的 NSN 之后执行此操作！页面最初可能包含一些对我们可见的元组，但被拆分，以便所有可见元组都移到了另一个页面，然后该页面被删除。
	 */
	if (GistPageIsDeleted(fc_page))
	{
		UnlockReleaseBuffer(fc_buffer);
		return;
	}

	fc_so->nPageData = fc_so->curPageData = 0;
	fc_scan->xs_hitup = NULL;		/* 可能指向 pageDataCxt */
	if (fc_so->pageDataCxt)
		MemoryContextReset(fc_so->pageDataCxt);

	/*
	 * 我们在读取页面时保存该页面的 LSN，以便之后知道是否可以安全地将 LP_DEAD 提示应用于该页面。这使得我们可以释放 MVCC 扫描的固定，这使得清理操作可以避免阻塞。
	 */
	fc_so->curPageLSN = BufferGetLSNAtomic(fc_buffer);

	/*
	 * 检查页面上的所有元组
	 */
	fc_maxoff = PageGetMaxOffsetNumber(fc_page);
	for (fc_i = FirstOffsetNumber; fc_i <= fc_maxoff; fc_i = OffsetNumberNext(fc_i))
	{
		ItemId		fc_iid = PageGetItemId(fc_page, fc_i);
		IndexTuple	fc_it;
		bool		fc_match;
		bool		fc_recheck;
		bool		fc_recheck_distances;

		/*
		 * 如果扫描指定不返回已删除的元组，则我们将已删除的元组视为未通过条件。
		 */
		if (fc_scan->ignore_killed_tuples && ItemIdIsDead(fc_iid))
			continue;

		fc_it = (IndexTuple) PageGetItem(fc_page, fc_iid);

		/*
		 * 必须在 tempCxt 中调用 gistindex_keytest，并在之后清理任何剩余的垃圾。
		 */
		fc_oldcxt = MemoryContextSwitchTo(fc_so->giststate->tempCxt);

		fc_match = fc_gistindex_keytest(fc_scan, fc_it, fc_page, fc_i,
								  &fc_recheck, &fc_recheck_distances);

		MemoryContextSwitchTo(fc_oldcxt);
		MemoryContextReset(fc_so->giststate->tempCxt);

		/* 如果不匹配则忽略元组 */
		if (!fc_match)
			continue;

		if (fc_tbm && GistPageIsLeaf(fc_page))
		{
			/*
			 * 获取位图扫描，因此只需将堆元组 TID 推入位图，不用担心顺序
			 */
			tbm_add_tuples(fc_tbm, &fc_it->t_tid, 1, fc_recheck);
			(*fc_ntids)++;
		}
		else if (fc_scan->numberOfOrderBys == 0 && GistPageIsLeaf(fc_page))
		{
			/*
			 * 非顺序扫描，因此报告 so->pageData[] 中的元组
			 */
			fc_so->pageData[fc_so->nPageData].heapPtr = fc_it->t_tid;
			fc_so->pageData[fc_so->nPageData].recheck = fc_recheck;
			fc_so->pageData[fc_so->nPageData].offnum = fc_i;

			/*
			 * 在仅索引扫描中，还要从元组中提取数据。重构的元组存储在 pageDataCxt 中。
			 */
			if (fc_scan->xs_want_itup)
			{
				fc_oldcxt = MemoryContextSwitchTo(fc_so->pageDataCxt);
				fc_so->pageData[fc_so->nPageData].recontup =
					gistFetchTuple(fc_giststate, fc_r, fc_it);
				MemoryContextSwitchTo(fc_oldcxt);
			}
			fc_so->nPageData++;
		}
		else
		{
			/*
			 * 必须将项目推入搜索队列。我们在任何较低索引页面到达这里，同时在进行有序搜索时也适用于堆元组。
			 */
			GISTSearchItem *fc_item;
			int			fc_nOrderBys = fc_scan->numberOfOrderBys;

			fc_oldcxt = MemoryContextSwitchTo(fc_so->queueCxt);

			/* 为此项目创建新的 GISTSearchItem */
			fc_item = palloc(SizeOfGISTSearchItem(fc_scan->numberOfOrderBys));

			if (GistPageIsLeaf(fc_page))
			{
				/* 创建堆元组 GISTSearchItem */
				fc_item->blkno = InvalidBlockNumber;
				fc_item->data.heap.heapPtr = fc_it->t_tid;
				fc_item->data.heap.recheck = fc_recheck;
				fc_item->data.heap.recheckDistances = fc_recheck_distances;

				/*
				 * 在仅索引扫描中，还要从元组中提取数据。
				 */
				if (fc_scan->xs_want_itup)
					fc_item->data.heap.recontup = gistFetchTuple(fc_giststate, fc_r, fc_it);
			}
			else
			{
				/* 创建索引页面 GISTSearchItem */
				fc_item->blkno = ItemPointerGetBlockNumber(&fc_it->t_tid);

				/*
				 * 当前页面的 LSN 是子页父页面的 lsn。我们只有共享锁，因此需要原子性地获取 LSN。
				 */
				fc_item->data.parentlsn = BufferGetLSNAtomic(fc_buffer);
			}

			/* 使用新距离数据将其插入队列 */
			memcpy(fc_item->distances, fc_so->distances,
				   sizeof(fc_item->distances[0]) * fc_nOrderBys);

			pairingheap_add(fc_so->queue, &fc_item->phNode);

			MemoryContextSwitchTo(fc_oldcxt);
		}
	}

	UnlockReleaseBuffer(fc_buffer);
}

/*
 * 从搜索队列中提取下一个项目（按顺序）
 *
 * 返回 GISTSearchItem 或 NULL。调用者在使用完成后必须释放该项。
 */
static GISTSearchItem *
fc_getNextGISTSearchItem(GISTScanOpaque fc_so)
{
	GISTSearchItem *fc_item;

	if (!pairingheap_is_empty(fc_so->queue))
	{
		fc_item = (GISTSearchItem *) pairingheap_remove_first(fc_so->queue);
	}
	else
	{
		/* 当两个堆均为空时完成 */
		fc_item = NULL;
	}

	/* 返回项目；调用者负责释放它 */
	return fc_item;
}

/*
 * 在有序搜索中获取下一个堆元组
 */
static bool fc_getNextNearest(IndexScanDesc fc_scan)
{
	GISTScanOpaque fc_so = (GISTScanOpaque) fc_scan->opaque;
	bool		fc_res = false;

	if (fc_scan->xs_hitup)
	{
		/* 释放先前返回的元组 */
		pfree(fc_scan->xs_hitup);
		fc_scan->xs_hitup = NULL;
	}

	do
	{
		GISTSearchItem *fc_item = fc_getNextGISTSearchItem(fc_so);

		if (!fc_item)
			break;

		if (GISTSearchItemIsHeap(*fc_item))
		{
			/* 在当前最小距离找到堆项 */
			fc_scan->xs_heaptid = fc_item->data.heap.heapPtr;
			fc_scan->xs_recheck = fc_item->data.heap.recheck;

			index_store_float8_orderby_distances(fc_scan, fc_so->orderByTypes,
												 fc_item->distances,
												 fc_item->data.heap.recheckDistances);

			/* 在仅索引扫描中，还要返回重构的元组。 */
			if (fc_scan->xs_want_itup)
				fc_scan->xs_hitup = fc_item->data.heap.recontup;
			fc_res = true;
		}
		else
		{
			/* 访问索引页面，将其项目提取到队列中 */
			CHECK_FOR_INTERRUPTS();

			fc_gistScanPage(fc_scan, fc_item, fc_item->distances, NULL, NULL);
		}

		pfree(fc_item);
	} while (!fc_res);

	return fc_res;
}

/*
 * gistgettuple() -- 获取扫描中的下一个元组
 */
bool gistgettuple(IndexScanDesc fc_scan, ScanDirection fc_dir)
{
	GISTScanOpaque fc_so = (GISTScanOpaque) fc_scan->opaque;

	if (fc_dir != ForwardScanDirection)
		elog(ERROR, "GiST only supports forward scan direction");

	if (!fc_so->qual_ok)
		return false;

	if (fc_so->firstCall)
	{
		/* 通过处理根页面开始扫描 */
		GISTSearchItem fc_fakeItem;

		pgstat_count_index_scan(fc_scan->indexRelation);

		fc_so->firstCall = false;
		fc_so->curPageData = fc_so->nPageData = 0;
		fc_scan->xs_hitup = NULL;
		if (fc_so->pageDataCxt)
			MemoryContextReset(fc_so->pageDataCxt);

		fc_fakeItem.blkno = GIST_ROOT_BLKNO;
		memset(&fc_fakeItem.data.parentlsn, 0, sizeof(GistNSN));
		fc_gistScanPage(fc_scan, &fc_fakeItem, NULL, NULL, NULL);
	}

	if (fc_scan->numberOfOrderBys > 0)
	{
		/* 必须严格按距离顺序获取元组 */
		return fc_getNextNearest(fc_scan);
	}
	else
	{
		/* 按索引页面逐个获取元组 */
		for (;;)
		{
			if (fc_so->curPageData < fc_so->nPageData)
			{
				if (fc_scan->kill_prior_tuple && fc_so->curPageData > 0)
				{

					if (fc_so->killedItems == NULL)
					{
						MemoryContext fc_oldCxt =
						MemoryContextSwitchTo(fc_so->giststate->scanCxt);

						fc_so->killedItems =
							(OffsetNumber *) palloc(MaxIndexTuplesPerPage
													* sizeof(OffsetNumber));

						MemoryContextSwitchTo(fc_oldCxt);
					}
					if (fc_so->numKilled < MaxIndexTuplesPerPage)
						fc_so->killedItems[fc_so->numKilled++] =
							fc_so->pageData[fc_so->curPageData - 1].offnum;
				}
				/* 继续从叶页面返回元组 */
				fc_scan->xs_heaptid = fc_so->pageData[fc_so->curPageData].heapPtr;
				fc_scan->xs_recheck = fc_so->pageData[fc_so->curPageData].recheck;

				/* 在仅索引扫描中，还要返回重构的元组 */
				if (fc_scan->xs_want_itup)
					fc_scan->xs_hitup = fc_so->pageData[fc_so->curPageData].recontup;

				fc_so->curPageData++;

				return true;
			}

			/*
			 * 检查最后返回的元组，并在必要时将其添加到 killedItems
			 */
			if (fc_scan->kill_prior_tuple
				&& fc_so->curPageData > 0
				&& fc_so->curPageData == fc_so->nPageData)
			{

				if (fc_so->killedItems == NULL)
				{
					MemoryContext fc_oldCxt =
					MemoryContextSwitchTo(fc_so->giststate->scanCxt);

					fc_so->killedItems =
						(OffsetNumber *) palloc(MaxIndexTuplesPerPage
												* sizeof(OffsetNumber));

					MemoryContextSwitchTo(fc_oldCxt);
				}
				if (fc_so->numKilled < MaxIndexTuplesPerPage)
					fc_so->killedItems[fc_so->numKilled++] =
						fc_so->pageData[fc_so->curPageData - 1].offnum;
			}
			/* 查找并处理下一个索引页面 */
			do
			{
				GISTSearchItem *fc_item;

				if ((fc_so->curBlkno != InvalidBlockNumber) && (fc_so->numKilled > 0))
					fc_gistkillitems(fc_scan);

				fc_item = fc_getNextGISTSearchItem(fc_so);

				if (!fc_item)
					return false;

				CHECK_FOR_INTERRUPTS();

				/* 保存当前项的 BlockNumber 以便下次调用 gistkillitems() */
				fc_so->curBlkno = fc_item->blkno;

				/*
				 * 在扫描叶子页面时，匹配堆
				 * 元组的 ItemPointers 存储在 so->pageData 中。如果在
				 * 这一页面上有任何，我们将跳出内部的 "do" 循环并
				 * 继续返回它们。
				 */
				fc_gistScanPage(fc_scan, fc_item, fc_item->distances, NULL, NULL);

				pfree(fc_item);
			} while (fc_so->nPageData == 0);
		}
	}
}

/*
 * gistgetbitmap() -- 获取所有堆元组位置的位图
 */
int64 gistgetbitmap(IndexScanDesc fc_scan, TIDBitmap *fc_tbm)
{
	GISTScanOpaque fc_so = (GISTScanOpaque) fc_scan->opaque;
	int64		fc_ntids = 0;
	GISTSearchItem fc_fakeItem;

	if (!fc_so->qual_ok)
		return 0;

	pgstat_count_index_scan(fc_scan->indexRelation);

	/* 通过处理根页面开始扫描 */
	fc_so->curPageData = fc_so->nPageData = 0;
	fc_scan->xs_hitup = NULL;
	if (fc_so->pageDataCxt)
		MemoryContextReset(fc_so->pageDataCxt);

	fc_fakeItem.blkno = GIST_ROOT_BLKNO;
	memset(&fc_fakeItem.data.parentlsn, 0, sizeof(GistNSN));
	fc_gistScanPage(fc_scan, &fc_fakeItem, NULL, fc_tbm, &fc_ntids);

	/*
	 * 在扫描叶子页面时，匹配堆元组的 ItemPointers 将
	 * 直接存储到 tbm 中，因此我们在这里不需要处理它们。
	 */
	for (;;)
	{
		GISTSearchItem *fc_item = fc_getNextGISTSearchItem(fc_so);

		if (!fc_item)
			break;

		CHECK_FOR_INTERRUPTS();

		fc_gistScanPage(fc_scan, fc_item, fc_item->distances, fc_tbm, &fc_ntids);

		pfree(fc_item);
	}

	return fc_ntids;
}

/*
 * 我们可以对给定索引列做索引仅扫描吗？
 *
 * 实现了获取函数的操作类支持索引仅扫描。
 * 没有压缩函数的操作类也支持索引仅扫描。
 * 包含的属性始终可以用于索引仅扫描。
 */
bool gistcanreturn(Relation fc_index, int fc_attno)
{
	if (fc_attno > IndexRelationGetNumberOfKeyAttributes(fc_index) ||
		OidIsValid(index_getprocid(fc_index, fc_attno, GIST_FETCH_PROC)) ||
		!OidIsValid(index_getprocid(fc_index, fc_attno, GIST_COMPRESS_PROC)))
		return true;
	else
		return false;
}
